#pragma once

#include <iostream>

#include <fstream>

#include <sstream>

#include <string>

#include <vector>

#include <sys/stat.h>

#include <stdlib.h>

#include <cstring>

#include <experimental/filesystem>

#include <jsoncpp/json/json.h>

#include "bundle.h"

#include <memory>

namespace Cloud{

    namespace  fs = std::experimental::filesystem ; // 简化命名

class FileUtil{
    private:
        std::string _filename;
    public:
        FileUtil(const std::string &filename):_filename(filename){
           //  std::cout<<"UTIL_HPP DEBUG > "<<_filename<<std::endl;
        }
        
        // 基本操作
        int64_t FileSize(); // 获取文件大小
        time_t LastMTime(); // 获取文件最后修改时间
        time_t LastATime(); // 获取文件最后访问时间
        std::string FileName(); // 获取文件路径中的文件名
        
        // 读写操作
        bool GetPosLen(std::string *body, size_t pos, size_t len); // 获取文件对应位置对应大小的数据
                                                                   // 主要用于实现断点续传功能
        bool GetContent(std::string *body); // 获取文件全部内容 传入输出型参数 读操作
        bool SetContent(const std::string &body); // 向文件内写入内容 写操作
        
        // 压缩解压缩操作
        bool Compress(const std::string &packname); // 压缩操作
        bool UnCompress(const std::string &unpackname); // 解压缩操作

        // 其他操作
        bool Exists(); // 判断文件是否存在
        bool CreateDirectory(); // 创建目录
        bool ScanDirectory(std::vector<std::string> *array); // 遍历目录 将目录以迭代器的形式进行遍历                       
        bool Remove(); // 删除操作

}; // class FileUtilUtil
   


int64_t FileUtil::FileSize(){
    // TODO - BUG!!!!!!!!!!!!!!!!!!!!!!!!!!HERE
    // TODO 这里传入了一个 /download/路径 所以找不到 找之前的问题 估计是在哪  
    // 参数传的有问题 
    struct stat fstat;                                                                                
                       
    std::cout<<"FileUtil > FileSize > _filename: "<<_filename<<std::endl;
    int ret = stat(_filename.c_str(),&fstat); // 获取文件信息 从 struct stat 结构体中获取 [ 参考man2手册 : stat()函数 ]
    if(ret < 0){
        std::cerr<<"Stat Error "<<ret<< " ,Error str: "<<strerror(errno)<<std::endl;
        exit(-1);
    }
    return fstat.st_size;
}

time_t FileUtil::LastMTime(){
    struct stat fstat;
    int ret = stat(_filename.c_str(),&fstat);
    if(ret < 0){
        std::cerr<<"Stat Error "<<ret<< " ,Error str: "<<strerror(errno)<<std::endl;
        exit(-1);
    }
    return fstat.st_mtime;
}

time_t FileUtil::LastATime(){
    struct stat fstat;
    int ret = stat(_filename.c_str(),&fstat);
    if(ret < 0){
           std::cerr<<"Stat Error "<<ret<< " ,Error str: "<<strerror(errno)<<std::endl;
           exit(-1);
       }
    return fstat.st_atime;
}

std::string FileUtil::FileName(){
    std::cout<<"Util.hpp - ToDebug FileName print _filename: "<<_filename<<std::endl;
    size_t pos = FileUtil::_filename.find_last_of("/"); // 从字符串末开始找
    if(pos == std::string::npos){ // 未找到
        //  std::cerr<<"FileName find error"<<std::endl;
        return FileUtil::_filename;
    }
    else{
        return FileUtil::_filename.substr(pos+1); // 由 pos+1 位置开始截断获取文件名
    }
}

bool FileUtil::GetPosLen(std::string *body, size_t pos, size_t len){
    // 判断需要读取的长度是否超过原始长度 如果超过则报错
    size_t fsize = this->FileSize();
    if(pos + len > fsize){
        std::cerr<<"Get partial file error"<<std::endl;
        return false;
    }

    std::ifstream ifs;
    ifs.open(_filename,std::ios::binary);
    if(!ifs.is_open()){ // 判断文件是否被打开
        std::cerr<<"Read open file error"<<std::endl;
        return false;
     }
    
    ifs.seekg(pos,std::ios::beg); // 将流从开头位置跳转到 pos 位置
    body->resize(len); // 初始化body大小
    ifs.read(&(*body)[0],len); // 先进行解引用再取地址首地址位置
    
    if(!ifs.good()){ // 判断流是否正常
        std::cerr<<"Get file content failed"<<std::endl;
        ifs.close();
        return false;
    }
    ifs.close();
    return true;
} 
                                                                   
bool FileUtil::GetContent(std::string *body){
    size_t fsize = this->FileSize(); // TODO - FileSize() error
    return FileUtil::GetPosLen(body,0,fsize); // 调用 GetPosLen() 实现数据获取
}
    
bool FileUtil::SetContent(const std::string &body){
    std::ofstream ofs; // 实例化写文件流
    ofs.open(_filename,std::ios::binary); // 以二进制流的方式打开一个文件
    if(!ofs.is_open()){
        std::cerr<<"Write open file error"<<std::endl;
        return false;
    }
    ofs.write(&body[0],body.size()); // 将数据写入至文件
    if(!ofs.good()){ // 判断流是否正常
        std::cerr<<"Set file content failed"<<std::endl; 
        ofs.close(); 
        return false;
    }
    ofs.close(); 
    return true;
}

bool FileUtil::Compress(const std::string &packname){
    // 压缩是对文件内容进行压缩
    std::string body;
    int ret = this->GetContent(&body); // 将文件信息进行获取
    if(!ret){
        std::cerr<<"Compress get file content failed"<<std::endl;
        return false;
    }

    std::string packed = bundle::pack(bundle::LZIP,body); // 文件操作已经完成 此处只需调用packed即可
    FileUtil fu(packname); // 打开一个文件用来写入压缩后的数据
    if(!fu.SetContent(packed)){ // 将压缩后的数据写入文件
        std::cerr<<"Compress setcontent failed"<<std::endl;
        return false;
    }
    return true;
}

bool FileUtil::UnCompress(const std::string &unpackname){
    std::string body; 
    std::cout<<"1 Util.hpp Debug - "<<""<< std::endl;
    int ret = this->GetContent(&body); // 将文件信息获取 // TODO  - 获取失败
    std::cout<<"2 Util.hpp Debug"<<std::endl;
    if(!ret){
        std::cerr<<"UnCompress get file content failed"<<std::endl;
        return false;
    }

    std::cout<<"3 Util.hpp Debug"<<std::endl;
    std::string unpacked = bundle::unpack(body); // 将压缩文件信息进行解压缩
    FileUtil fu(unpackname); // 打开一个文件用来写入解压后的数据
    if(!fu.SetContent(unpacked)){ // 将解压后的文件数据写入文件
        std::cerr<<"UnCompress setcontent failed"<<std::endl;
        return false;
    }
    return true;
}

bool FileUtil::Exists(){
    return fs::exists(_filename);
}

bool FileUtil::CreateDirectory(){
    if(!this->Exists()){
        return fs::create_directories(_filename); // 创建目录文件
    }
    return false;
} 
        
bool FileUtil::ScanDirectory(std::vector<std::string> *array){
    // for(auto& p: fs::recursive_directory_iterator(_filename)){} // 与 directory_iterator 类似 不同的是该接口为递归遍历
    
    // 该接口是专门针对目录用的 当需要遍历目录时
    // 使用 FileUtil 打开一个目录 并调用该函数即可

    for(auto& p: fs::directory_iterator(_filename)){ // 在构造 directory_iterator 时需要传入一个 path 对象
                                                     // 而 path 对象的构造可以采用 const string && 移动构造来构造 因此可以直接传入临时 std::string
        
        if(fs::is_directory(p)) continue; // 判断文件是不是目录文件
        
        array->push_back(fs::path(p).relative_path().string());   // √                
        // 调用 path 对象的 string() 方法将其转化为字符串对象
        // 这里需要实例化一个 path 的原因是遍历的目录中每个被遍历的单位并不是 path 对象
        // 而是 std::filesystem::directory_entry 
        // 因此需要利用 directory_entry 构造出一个 path 对象才能调用 对应的 relative_path 
       
        // array->push_back(p.path().relative_path().string());   // √               
    }

    return true;
}                     

bool FileUtil::Remove(){
    if(!this->Exists()){
        return true;
    }
    remove(_filename.c_str());
    return true;
}
               


class JsonUtil{
    public:
    static bool Serialize(const Json::Value &root, std::string *str); // 序列化
    static bool UnSerialize(const std::string &str, Json::Value *root); // 反序列化
};

bool JsonUtil::Serialize(const Json::Value &root, std::string *str){
    // 将所有数据保存在Json::Value中
    // 使用Json::StreamWriter , Json::StreamWriterBuilder 进行序列化与反
    // 序列化采用接口 write:
    // virtual int write(Value const& root, std::ostream* out) = 0
    // 其中 std::ostream* out 能够使用 std::stringstream 进行序列化;

    Json::StreamWriterBuilder swb;
    // swb.settings_["emitUTF8"] = true;
    /* Jsoncpp 默认行为 非ASCII字符会转义为Unicode */
    /* 改为采用UTF-8的形式进行序列化而不采用Uincode */
    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
    std::stringstream ss;
    int ret = sw->write(root,&ss);
    if(ret != 0){
        std::cerr<<"Serialize write failed"<<std::endl;
        return false;
    }

    *str = ss.str();
    return true;
}
bool JsonUtil::UnSerialize(const std::string &str, Json::Value *root){

     /* *
     * 反序列化:
     * 反序列首先需要有一个字符串 这个字符串为一个Json格式的字符串
     * Jsoncpp中进行反序列化是将字符串中的数据解析放到对应的Json::Valu
     *   1. 定义一个Json::Value
     *   2. 借助Json::CharReaderBuilder,Json::CharReader类完成反序列
     *   调用CharReader中的bool parse()成员函数进行反序列化
     *   bool parse(char const*beginDoc, char const* endDoc, Value* 
     *   std::string* errs) = 0;
     * */
    
    std::string strerr;
            Json::CharReaderBuilder crb;
        std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
        bool ret = cr->parse(str.c_str(),str.c_str()+str.size(),root,&strerr);
        if(!ret){
            // 反序列化失败
            std::cerr<<"UnSerialize parse failed"<<std::endl;
            return false;
        }
        return true;
}



} // Namespace Cloud

